import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.jar.*;
import java.util.random.RandomGenerator;
import java.util.zip.Deflater;

/**
 * Java代码与配置文件加密工具
 * <p>
 * 功能说明：
 * 本工具使用DES加密算法和固定密钥"test"，对指定类型的文件进行加密保护。
 * 支持加密的文件类型包括：Java源代码(.java)、YAML配置(.yaml/.yml)和XML配置(.xml)。
 * 加密后所有文件统一转换为.class扩展名，解密时自动恢复原始文件类型和内容。
 * 工具还支持将加密文件打包为JAR，并通过日志记录所有操作过程。
 * <p>
 * 使用方法：
 * 1. 编译：javac -encoding UTF-8 JavaEncryptTool.java
 * 2. 运行：java JavaEncryptTool
 * 3. 按照提示选择加密/解密模式，输入目录路径即可自动处理
 */
public sealed class JavaEncryptTool permits EncryptToolExtension {
    // 固定加密密钥（8字节，符合DES算法要求）
    protected static final String FIXED_KEY = "TEST";
    // 加密文件标记前缀（用于验证文件合法性）
    private static final String ENCRYPT_MARKER_PREFIX = "#JAVA_DES_TEST:";
    // 加密文件标记后缀
    private static final String ENCRYPT_MARKER_SUFFIX = "#";
    // 加密算法配置（DES算法/ECB模式/PKCS5填充）
    private static final String ENCRYPTION_ALGORITHM = "DES/ECB/PKCS5Padding";
    // 支持加密的文件扩展名集合
    private static final Set<String> SUPPORTED_EXTENSIONS = Set.of(".java", ".yaml", ".yml", ".xml");
    // 随机字符集（用于生成加密干扰数据）
    private static final String RANDOM_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~!@#$%^&*()_+-=[]{}|;':\",./<>?`";
    // 随机数生成器
    private static final RandomGenerator RANDOM = RandomGenerator.getDefault();
    // 临时目录（用于JAR打包前的文件处理）
    private static final String TEMP_DIR = "encrypt_temp";
    // 日志文件夹
    private static final String LOG_FOLDER = "encrypt_logs";
    // 日志文件路径（包含时间戳，避免文件覆盖）
    private static final String LOG_FILE = LOG_FOLDER + File.separator +
            "encrypt_log_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")) + ".txt";
    private static final List<String> failedFiles = new ArrayList<>();  // 失败文件列表
    // 加密密钥实例（程序启动时初始化）
    private static final SecretKey SECRET_KEY;
    // 处理统计信息
    private static int totalFilesProcessed = 0;   // 总处理文件数
    private static int successCount = 0;          // 成功处理数
    private static int failureCount = 0;          // 失败处理数

    /**
     * 静态初始化块：初始化加密密钥
     * DES算法要求密钥长度必须为8字节，此处使用固定密钥"TEST"
     */
    static {
        try {
            // 创建DES密钥规范
            DESKeySpec keySpec = new DESKeySpec(FIXED_KEY.getBytes(StandardCharsets.UTF_8));
            // 获取密钥工厂实例
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
            // 生成实际使用的密钥对象
            SECRET_KEY = keyFactory.generateSecret(keySpec);
        } catch (InvalidKeyException | NoSuchAlgorithmException | InvalidKeySpecException e) {
            // 密钥初始化失败时抛出运行时异常，终止程序
            throw new RuntimeException("加密密钥初始化失败: " + e.getMessage());
        }
    }

    /**
     * 程序主入口
     */
    public static void main(String[] args) {
        // 确保日志文件夹存在
        ensureLogFolderExists();

        long startTime = System.currentTimeMillis();  // 记录开始时间
        Scanner scanner = new Scanner(System.in, StandardCharsets.UTF_8.name());  // 字符集设置为UTF-8

        try {
            // 打印程序标题信息
            System.out.println("==================================");
            System.out.println("     Java文件加密工具 v1.0         ");
            System.out.println("     支持类型: .java .yaml .yml .xml");
            System.out.println("     加密后后缀: .class             ");
            System.out.println("==================================\n");

            // 选择操作模式（加密/解密）
            OperationType operation = selectOperationMode(scanner);
            // 输入目标目录
            String targetDir = inputTargetDirectory(scanner, operation);

            // 加密模式下输入JAR输出路径
            String outputJar = "encrypted_files.jar";
            if (operation == OperationType.ENCRYPT) {
                outputJar = inputOutputJarPath(scanner);
            }

            // 确认操作
            if (!confirmOperation(scanner, operation, targetDir, outputJar)) {
                System.out.println("操作已取消");
                return;
            }

            // 记录操作开始日志
            log("=== 开始执行加密工具 ===");
            log("操作类型: " + operation.getDescription());
            log("目标目录: " + Path.of(targetDir).toAbsolutePath());
            if (operation == OperationType.ENCRYPT) {
                log("输出JAR: " + Path.of(outputJar).toAbsolutePath());
            }
            log("开始时间: " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

            // 执行核心操作
            if (operation == OperationType.ENCRYPT) {
                // 处理目录中的文件（加密）
                processDirectory(Path.of(targetDir), operation);
                // 将加密文件打包为JAR
                createJarFromEncryptedFiles(targetDir, outputJar);
                log("JAR文件生成路径: " + Path.of(outputJar).toAbsolutePath());
            } else {
                // 处理目录中的文件（解密）
                processDirectory(Path.of(targetDir), operation);
            }

            // 打印操作统计信息
            printStatistics(startTime, operation);

        } catch (Exception e) {
            // 捕获并处理操作过程中的异常
            String errorMsg = "操作失败：" + e.getMessage();
            System.err.println(errorMsg);
            log(errorMsg);
            e.printStackTrace();
        } finally {
            // 资源清理
            scanner.close();
            try {
                // 删除临时目录
                deleteDirectory(Path.of(TEMP_DIR));
                log("临时目录已清理");
            } catch (Exception e) {
                log("清理临时文件失败: " + e.getMessage());
            }

            // 记录操作结束日志
            log("结束时间: " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            log("=== 操作结束 ===");
        }
    }

    /**
     * 加密字符串，包含原文件类型信息
     *
     * @param data              要加密的原始内容
     * @param originalExtension 原文件的扩展名（如.java, .xml）
     * @return 加密后的字符串
     * @throws Exception 加密过程中可能出现的异常
     */
    private static String encrypt(String data, String originalExtension) throws Exception {
        // 获取加密实例
        Cipher cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM);
        // 初始化加密模式和密钥
        cipher.init(Cipher.ENCRYPT_MODE, SECRET_KEY);
        // 执行加密操作
        byte[] encryptedData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));

        // 生成随机前缀和后缀（各8字符），增加破解难度
        String randomPrefix = generateRandomRedundancy(8);
        String randomSuffix = generateRandomRedundancy(8);

        // 生成包含原文件类型的标记（用于解密时恢复文件类型）
        String typeMarker = ENCRYPT_MARKER_PREFIX + originalExtension + ENCRYPT_MARKER_SUFFIX;

        // 组合加密数据：类型标记 + 随机前缀 + Base64加密数据 + 随机后缀 + 类型标记
        return typeMarker + randomPrefix +
                Base64.getEncoder().encodeToString(encryptedData) +
                randomSuffix + typeMarker;
    }

    /**
     * 解密字符串，同时提取原文件类型
     *
     * @param encryptedData 加密后的字符串
     * @return 包含解密内容和原文件扩展名的DecryptResult对象
     * @throws Exception 解密过程中可能出现的异常
     */
    private static DecryptResult decrypt(String encryptedData) throws Exception {
        // 查找并验证加密标记
        int startMarkerIndex = encryptedData.indexOf(ENCRYPT_MARKER_PREFIX);
        int endMarkerIndex = encryptedData.indexOf(ENCRYPT_MARKER_SUFFIX, startMarkerIndex + ENCRYPT_MARKER_PREFIX.length());

        // 验证标记是否存在
        if (startMarkerIndex == -1 || endMarkerIndex == -1) {
            throw new IllegalArgumentException("无效的加密文件，缺少类型标记");
        }

        // 提取原文件类型（从标记中获取）
        String originalExtension = encryptedData.substring(
                startMarkerIndex + ENCRYPT_MARKER_PREFIX.length(),
                endMarkerIndex
        );

        // 验证文件类型是否受支持
        if (!SUPPORTED_EXTENSIONS.contains(originalExtension)) {
            throw new IllegalArgumentException("不支持的文件类型: " + originalExtension);
        }

        // 验证首尾标记一致性
        String fullMarker = ENCRYPT_MARKER_PREFIX + originalExtension + ENCRYPT_MARKER_SUFFIX;
        if (!encryptedData.startsWith(fullMarker) || !encryptedData.endsWith(fullMarker)) {
            throw new IllegalArgumentException("加密文件标记不一致，可能已被篡改");
        }

        // 提取核心加密数据（移除首尾标记）
        String coreData = encryptedData.replace(fullMarker, "").trim();

        // 检查数据长度（至少需要16字符的随机数据 + 加密内容）
        if (coreData.length() < 16) {
            throw new IllegalArgumentException("加密数据损坏，长度不足");
        }

        // 移除随机前缀和后缀（各8字符）
        String encodedData = coreData.substring(8, coreData.length() - 8);

        // 执行解密操作
        Cipher cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, SECRET_KEY);
        byte[] decryptedData = cipher.doFinal(Base64.getDecoder().decode(encodedData));

        // 返回解密结果和原文件类型
        return new DecryptResult(new String(decryptedData, StandardCharsets.UTF_8), originalExtension);
    }

    /**
     * 确保日志文件夹存在，不存在则创建
     */
    private static void ensureLogFolderExists() {
        File logFolder = new File(LOG_FOLDER);
        if (!logFolder.exists() && !logFolder.mkdirs()) {
            System.err.println("警告: 无法创建日志文件夹，日志将存储在当前目录");
        }
    }

    /**
     * 让用户选择操作模式（加密或解密）
     *
     * @param scanner 输入扫描器
     * @return 选择的操作模式
     */
    private static OperationType selectOperationMode(Scanner scanner) {
        while (true) {
            System.out.println("请选择操作模式:");
            System.out.println("1 - 加密模式（.java/.yaml/.yml/.xml → .class）");
            System.out.println("2 - 解密模式（.class → 原文件类型）");
            System.out.print("请输入数字(1/2): ");

            String input = scanner.nextLine().trim();
            if ("1".equals(input)) {
                return OperationType.ENCRYPT;
            } else if ("2".equals(input)) {
                return OperationType.DECRYPT;
            } else {
                System.out.println("无效输入，请重新选择！\n");
            }
        }
    }

    /**
     * 获取用户输入的目标目录路径
     *
     * @param scanner   输入扫描器
     * @param operation 当前操作类型
     * @return 目标目录路径
     */
    private static String inputTargetDirectory(Scanner scanner, OperationType operation) {
        while (true) {
            // 根据操作类型显示不同的提示信息
            String fileTypeDesc = operation == OperationType.ENCRYPT
                    ? "Java源代码或配置文件(.java/.yaml/.yml/.xml)"
                    : "加密的.class";
            System.out.print("\n请输入" + fileTypeDesc + "文件目录路径: ");
            String dirPath = scanner.nextLine().trim();

            Path path = Path.of(dirPath);
            // 验证目录是否有效
            if (Files.isDirectory(path)) {
                return dirPath;
            } else {
                System.out.println("错误：目录不存在或不是有效的目录！");
                System.out.print("是否重新输入？(y/n): ");
                String confirm = scanner.nextLine().trim().toLowerCase();
                if (!"y".equals(confirm)) {
                    throw new RuntimeException("用户取消操作");
                }
            }
        }
    }

    /**
     * 获取用户输入的JAR输出路径（加密模式）
     *
     * @param scanner 输入扫描器
     * @return JAR文件路径
     */
    private static String inputOutputJarPath(Scanner scanner) {
        System.out.print("\n请输入输出JAR文件路径（默认: encrypted_files.jar）: ");
        String jarPath = scanner.nextLine().trim();

        // 使用默认路径如果用户未输入
        if (jarPath.isEmpty()) {
            return "encrypted_files.jar";
        }

        Path path = Path.of(jarPath);
        Path parentDir = path.getParent();
        // 检查父目录是否存在，不存在则提示
        if (parentDir != null && !Files.isDirectory(parentDir)) {
            System.out.println("警告：JAR文件所在目录不存在，将自动创建");
        }

        return jarPath;
    }

    /**
     * 让用户确认操作信息
     *
     * @param scanner   输入扫描器
     * @param operation 操作类型
     * @param targetDir 目标目录
     * @param outputJar 输出JAR路径
     * @return 是否确认操作
     */
    private static boolean confirmOperation(Scanner scanner, OperationType operation, String targetDir, String outputJar) {
        System.out.println("\n===== 操作确认 =====");
        System.out.println("操作模式: " + operation.getDescription());
        System.out.println("目标目录: " + Path.of(targetDir).toAbsolutePath());
        if (operation == OperationType.ENCRYPT) {
            System.out.println("输出JAR: " + Path.of(outputJar).toAbsolutePath());
        }
        System.out.print("是否确认执行？(y/n): ");

        String confirm = scanner.nextLine().trim().toLowerCase();
        return "y".equals(confirm);
    }

    /**
     * 处理目录中的所有文件（递归遍历）
     *
     * @param dir       要处理的目录
     * @param operation 操作类型（加密/解密）
     * @throws IOException 可能的IO异常
     */
    private static void processDirectory(Path dir, OperationType operation) throws IOException {
        log("开始处理目录: " + dir.toAbsolutePath());

        // 使用文件树遍历处理所有文件
        Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                String fileName = file.getFileName().toString();

                if (operation == OperationType.ENCRYPT) {
                    // 处理加密：检查是否为支持的文件类型
                    String extension = getFileExtension(fileName);
                    if (SUPPORTED_EXTENSIONS.contains(extension)) {
                        totalFilesProcessed++;
                        try {
                            encryptFile(file, extension);
                            successCount++;
                        } catch (Exception e) {
                            failureCount++;
                            failedFiles.add(file.toAbsolutePath().toString());
                            log("加密失败 [" + fileName + "]: " + e.getMessage());
                        }
                    }
                } else if (operation == OperationType.DECRYPT && fileName.endsWith(".class")) {
                    // 处理解密：检查是否为加密文件
                    try {
                        String content = Files.readString(file, StandardCharsets.UTF_8);
                        if (content.contains(ENCRYPT_MARKER_PREFIX)) {
                            totalFilesProcessed++;
                            try {
                                decryptEncryptedFile(file);
                                successCount++;
                            } catch (Exception e) {
                                failureCount++;
                                failedFiles.add(file.toAbsolutePath().toString());
                                log("解密失败 [" + fileName + "]: " + e.getMessage());
                            }
                        } else {
                            log("跳过非加密文件: " + fileName);
                        }
                    } catch (Exception e) {
                        log("无法读取文件内容: " + fileName + ", 已跳过");
                    }
                }

                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
                String errorMsg = "无法访问文件，已跳过：" + file;
                System.err.println(errorMsg);
                log(errorMsg);
                return FileVisitResult.CONTINUE;
            }
        });

        log("目录处理完成: " + dir.toAbsolutePath());
    }

    /**
     * 获取文件的扩展名（包含点号）
     *
     * @param fileName 文件名
     * @return 文件扩展名（小写），如".java"
     */
    private static String getFileExtension(String fileName) {
        int lastDotIndex = fileName.lastIndexOf('.');
        if (lastDotIndex > 0 && lastDotIndex < fileName.length() - 1) {
            return fileName.substring(lastDotIndex).toLowerCase();
        }
        return "";
    }

    /**
     * 将支持的文件类型加密为.class文件
     *
     * @param sourceFile        源文件路径
     * @param originalExtension 源文件扩展名
     * @throws Exception 加密过程中可能出现的异常
     */
    private static void encryptFile(Path sourceFile, String originalExtension) throws Exception {
        // 创建目标文件路径，使用.class后缀
        String fileName = sourceFile.getFileName().toString();
        String baseName = fileName.substring(0, fileName.lastIndexOf('.'));
        String encryptedFileName = baseName + ".class";
        Path encryptedFile = sourceFile.resolveSibling(encryptedFileName);

        // 读取文件内容（保持原始结构和格式）
        String fileContent = Files.readString(sourceFile, StandardCharsets.UTF_8);

        // 执行加密，包含原文件类型信息
        String encryptedContent = encrypt(fileContent, originalExtension);

        // 写入加密文件
        Files.writeString(encryptedFile, encryptedContent, StandardCharsets.UTF_8);

        // 随机调整文件时间戳（增加文件伪装性）
        Instant now = Instant.now();
        long randomDays = RANDOM.nextLong(60) - 30; // -30到30天之间的随机值
        Instant newTime = now.plus(randomDays, ChronoUnit.DAYS);
        Files.setLastModifiedTime(encryptedFile, FileTime.from(newTime));

        // 删除原文件（如需保留原文件，可注释此行）
        Files.deleteIfExists(sourceFile);

        String message = "加密完成：" + sourceFile.getFileName() + " → " + encryptedFile.getFileName();
        System.out.println(message);
        log(message);
    }

    /**
     * 将加密的.class文件解密为原始文件类型
     *
     * @param encryptedFile 加密文件路径
     * @throws Exception 解密过程中可能出现的异常
     */
    private static void decryptEncryptedFile(Path encryptedFile) throws Exception {
        // 读取加密文件内容
        String encryptedContent = Files.readString(encryptedFile, StandardCharsets.UTF_8);

        // 执行解密，获取原始内容和文件类型
        DecryptResult result = decrypt(encryptedContent);
        String originalContent = result.getContent();
        String originalExtension = result.getOriginalExtension();

        // 创建目标文件路径，恢复原始扩展名
        String fileName = encryptedFile.getFileName().toString();
        String baseName = fileName.substring(0, fileName.lastIndexOf('.'));
        String originalFileName = baseName + originalExtension;
        Path originalFile = encryptedFile.resolveSibling(originalFileName);

        // 写入还原文件（保持原始结构和格式）
        Files.writeString(originalFile, originalContent, StandardCharsets.UTF_8);

        // 恢复时间戳为当前时间
        Files.setLastModifiedTime(originalFile, FileTime.from(Instant.now()));

        // 删除原加密文件（如需保留加密文件，可注释此行）
        Files.deleteIfExists(encryptedFile);

        String message = "解密完成：" + encryptedFile.getFileName() + " → " + originalFile.getFileName();
        System.out.println(message);
        log(message);
    }

    /**
     * 将加密的.class文件打包为JAR（优化压缩版本）
     *
     * @param sourceDir     源目录
     * @param outputJarPath 输出JAR文件路径
     * @throws IOException 可能的IO异常
     */
    private static void createJarFromEncryptedFiles(String sourceDir, String outputJarPath) throws IOException {
        log("开始打包JAR文件: " + outputJarPath);

        // 创建临时目录
        Path tempDir = Path.of(TEMP_DIR);
        Files.createDirectories(tempDir);
        log("创建临时目录: " + tempDir.toAbsolutePath());

        // 复制并预处理加密文件
        Path sourcePath = Path.of(sourceDir);
        final int[] jarFileCount = {0};

        Files.walkFileTree(sourcePath, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                String fileName = file.getFileName().toString();
                if (fileName.endsWith(".class")) {
                    try {
                        String content = Files.readString(file, StandardCharsets.UTF_8);
                        if (content.contains(ENCRYPT_MARKER_PREFIX)) {
                            // 优化加密内容，移除多余空行
                            String optimizedContent = optimizeEncryptedContent(content);

                            Path relativePath = sourcePath.relativize(file);
                            Path targetFile = tempDir.resolve(relativePath);

                            Files.createDirectories(targetFile.getParent());
                            Files.writeString(targetFile, optimizedContent, StandardCharsets.UTF_8);
                            jarFileCount[0]++;
                        }
                    } catch (Exception e) {
                        log("无法处理文件: " + fileName + ", 已跳过");
                    }
                }
                return FileVisitResult.CONTINUE;
            }
        });

        log("准备打包 " + jarFileCount[0] + " 个加密文件到JAR");

        // 创建Manifest文件（JAR清单）
        Manifest manifest = new Manifest();
        manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
        manifest.getMainAttributes().put(new Attributes.Name("Created-By"), "JavaEncryptTool");
        manifest.getMainAttributes().put(new Attributes.Name("Encryption"), "DES with key: " + FIXED_KEY);
        manifest.getMainAttributes().put(new Attributes.Name("Supported-Types"), ".java,.yaml,.yml,.xml");
        manifest.getMainAttributes().put(new Attributes.Name("Build-Time"), Instant.now().toString());
        manifest.getMainAttributes().put(new Attributes.Name("File-Count"), String.valueOf(jarFileCount[0]));

        // 创建JAR文件，使用最高压缩级别
        try (OutputStream os = Files.newOutputStream(Path.of(outputJarPath));
             BufferedOutputStream bos = new BufferedOutputStream(os);
             // 设置最高压缩级别
             JarOutputStream jarOut = new JarOutputStream(bos, manifest) {
                 {
                     setLevel(Deflater.BEST_COMPRESSION);
                 }
             }) {

            // 添加文件到JAR
            Files.walkFileTree(tempDir, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    String entryName = tempDir.relativize(file).toString().replace(File.separator, "/");
                    JarEntry entry = new JarEntry(entryName);
                    // 移除时间戳元数据以减小体积
                    entry.setTime(0);

                    jarOut.putNextEntry(entry);
                    Files.copy(file, jarOut);
                    jarOut.closeEntry();

                    return FileVisitResult.CONTINUE;
                }
            });
        }

        log("JAR文件打包完成: " + outputJarPath + " (" + jarFileCount[0] + " 个文件)");
    }

    /**
     * 优化加密内容，移除多余空白字符以减小体积
     *
     * @param content 加密后的内容
     * @return 优化后的加密内容
     */
    private static String optimizeEncryptedContent(String content) {
        // 仅移除多余空行和首尾空白，不影响加密数据结构
        return content.replaceAll("\\n+", "\n")  // 合并连续空行
                .replaceAll("^\\s+", "")   // 移除开头空白
                .replaceAll("\\s+$", "");  // 移除结尾空白
    }

    /**
     * 生成指定长度的随机冗余字符串
     *
     * @param len 字符串长度
     * @return 随机字符串
     */
    private static String generateRandomRedundancy(int len) {
        StringBuilder sb = new StringBuilder(len);
        for (int i = 0; i < len; i++) {
            sb.append(RANDOM_CHARS.charAt(RANDOM.nextInt(RANDOM_CHARS.length())));
        }
        return sb.toString();
    }

    /**
     * 删除目录及其所有内容（递归删除）
     *
     * @param dir 要删除的目录
     */
    private static void deleteDirectory(Path dir) {
        if (Files.exists(dir)) {
            try {
                Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
                    @Override
                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                        Files.delete(file);
                        return FileVisitResult.CONTINUE;
                    }

                    @Override
                    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                        Files.delete(dir);
                        return FileVisitResult.CONTINUE;
                    }
                });
            } catch (IOException e) {
                log("清理临时文件失败：" + e.getMessage());
            }
        }
    }

    /**
     * 记录日志到文件
     *
     * @param message 日志消息
     */
    private static void log(String message) {
        try {
            ensureLogFolderExists();
            Path logPath = Path.of(LOG_FILE);

            // 日志格式：[时间戳] 消息
            String logEntry = "[" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + "] " + message + "\n";

            // 追加写入日志
            Files.writeString(logPath, logEntry, StandardCharsets.UTF_8,
                    StandardOpenOption.CREATE, StandardOpenOption.APPEND);
        } catch (IOException e) {
            System.err.println("日志记录失败: " + e.getMessage());
        }
    }

    /**
     * 打印操作统计信息
     *
     * @param startTime 操作开始时间（毫秒）
     * @param operation 操作类型
     */
    private static void printStatistics(long startTime, OperationType operation) {
        long endTime = System.currentTimeMillis();
        long durationSeconds = (endTime - startTime) / 1000;

        String stats = "\n===== 操作统计信息 =====\n" +
                "操作类型: " + operation.getDescription() + "\n" +
                "总处理文件数: " + totalFilesProcessed + "\n" +
                "成功数: " + successCount + "\n" +
                "失败数: " + failureCount + "\n" +
                "耗时: " + durationSeconds + " 秒\n" +
                "日志文件: " + Path.of(LOG_FILE).toAbsolutePath() + "\n" +
                "=======================";

        System.out.println(stats);
        log(stats);

        // 打印失败文件列表（如果有）
        if (failureCount > 0) {
            StringBuilder failedList = new StringBuilder("\n失败文件列表:\n");
            for (String file : failedFiles) {
                failedList.append("- ").append(file).append("\n");
            }
            System.err.println(failedList);
            log(failedList.toString());
        }
    }

    /**
     * 操作类型枚举
     */
    private enum OperationType {
        ENCRYPT("加密模式（.java/.yaml/.yml/.xml → .class）"),
        DECRYPT("解密模式（.class → 原文件类型）");

        private final String description;

        OperationType(String description) {
            this.description = description;
        }

        public String getDescription() {
            return description;
        }
    }

    /**
     * 解密结果封装类，包含解密内容和原文件扩展名
     */
    private static class DecryptResult {
        private final String content;          // 解密后的文件内容
        private final String originalExtension; // 原文件的扩展名

        public DecryptResult(String content, String originalExtension) {
            this.content = content;
            this.originalExtension = originalExtension;
        }

        public String getContent() {
            return content;
        }

        public String getOriginalExtension() {
            return originalExtension;
        }
    }
}

/**
 * 加密工具扩展类，提供JAR验证功能
 */
non-sealed class EncryptToolExtension extends JavaEncryptTool {
    /**
     * 验证JAR文件是否为本工具生成
     *
     * @param jarPath JAR文件路径
     * @return 如果是本工具生成的JAR返回true，否则返回false
     * @throws IOException 可能的IO异常
     */
    public boolean verifyJar(String jarPath) throws IOException {
        try (JarFile jarFile = new JarFile(jarPath)) {
            Manifest manifest = jarFile.getManifest();
            if (manifest == null) return false;

            // 验证清单中的加密信息和支持的文件类型
            String encryptionInfo = manifest.getMainAttributes().getValue("Encryption");
            String supportedTypes = manifest.getMainAttributes().getValue("Supported-Types");

            return encryptionInfo != null && encryptionInfo.contains("DES with key: " + FIXED_KEY) &&
                    supportedTypes != null && supportedTypes.contains(".java,.yaml,.yml,.xml");
        }
    }
}
